package REMC.replicaExchange;

import java.util.Iterator;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

import main.Configuration;
import REMC.monteCarlo.MonteCarloSearch;
import REMC.objects.CVector;
import REMC.objects.MyLogger;
import REMC.objects.Replica;
/**
 * @authors Tal Cohen & Adiel Ashrov
 * this class represents the Replica Exchange algorithm
 */
public class ReplicaExchange {

	private int replicaExchangeLoopSize;
	private int e_Star;
	private Double emitLastFraction;
	private MonteCarloSearch mcs;
	private Vector<ReplicaStatisticsHandler> statsVec; //to collect statistics for report
	private Integer countSwapsLabels; //to collect statistics for report
	
	/**
	 * constractor
	 * @param config configuration file is at out/config.ini
	 */
	public ReplicaExchange(Configuration config){
		replicaExchangeLoopSize = config.replicaExchangeLoopSize;
		e_Star = config.replicaExchangeEStar;
		emitLastFraction = config.emitLastFraction;
		mcs = new MonteCarloSearch(config, false);
		statsVec = new Vector<ReplicaStatisticsHandler>();
		countSwapsLabels = new Integer(0);
	}

	/**
	 * performing the replica exchange algorithm itself!
	 * @param cVec  a vector of replicas to execute the algorithm on.
	 * @return the Vector of replicas after the algorithm.
	 */
	public CVector simulation(CVector cVec){

		//initializing ReplicaStatisticsHandler for each temp in vec
		for (int i = 0; i < cVec.size(); i++) {
			Double temp = cVec.get(i).get_temperature();
			ReplicaStatisticsHandler stat = new ReplicaStatisticsHandler(temp);
			statsVec.add(i, stat);
		}

		int e = 0;
		int offset = 0; // toggle bit, makes sure that one time we start swaps from index 0 and one time from 1
		int loopCount = 0;
		int i,j;
		double delta, q, boltzman;
		//collecting time statistics:
		long startMSCTime,startReplicaTime,endMSCTime,endReplicaTime,millis;
		startReplicaTime = System.currentTimeMillis();
		
		while(e > e_Star && loopCount < replicaExchangeLoopSize){
			MyLogger.getLogger().Log("****************Starting loop: " + loopCount + " ******************");
			for(int l = 0 ; l < cVec.size();l++ ){
				Replica tReplica = cVec.get(l);
				
				//running monte carlo search for current replica
				startMSCTime = System.currentTimeMillis(); //msc start time for replica
				tReplica = mcs.search(tReplica); 
				endMSCTime = System.currentTimeMillis(); //msc end time for replica
				millis = endMSCTime - startMSCTime;
				String elapsed = String.format("%d min, %d sec", 
						TimeUnit.MILLISECONDS.toMinutes(millis),
						TimeUnit.MILLISECONDS.toSeconds(millis) - 
						TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
						);
				MyLogger.getLogger().Log("Finished Monte Carlo search of Replica[" + l + "]" 
						+ " At loop " + loopCount + ", Took us: " + elapsed);
				cVec.set(l, tReplica);
				
				//checking if monte carlo improved the energy
				if(tReplica.getEnergy() < e){
					e = (int)tReplica.getEnergy();
				}
			}//end for
			i = offset;
			while(i+1 <= cVec.size()-1){
				j = i + 1; //j is the index of the partner of i for the possible label swap 
				delta = (cVec.get(j).getBeta() - cVec.get(i).getBeta())*(cVec.get(i).getEnergy()- cVec.get(j).getEnergy()); 
				if (delta <= 0){
					MyLogger.getLogger().Log("Swap Labels between " + i + "," + j);
					cVec.swapLables(i, j);
					countSwapsLabels++;
				}else{
					q = Math.random();
					boltzman = Math.pow(Math.E, -delta);
					if(q <= boltzman){
						MyLogger.getLogger().Log("Swap Labels between " + i + "," + j);
						cVec.swapLables(i, j);
						countSwapsLabels++;
					}
				}
				i = i + 2; // moving on to the next couple of replicas
			}
			offset = 1 - offset; //flipping the toggle bit.
			loopCount++;
			
			//collecting fitness per temperature for current iteration
			if(loopCount >= (replicaExchangeLoopSize*(1-emitLastFraction))){
				for (int k = 0; k < cVec.size(); k++) {
					int fitness = (int)cVec.get(k).getFitness();
					statsVec.get(k).addStat(fitness);
				}
			}
			//printing statistics collected until now
			MyLogger.getLogger().Log("Current Number Of swaps: " +countSwapsLabels);
			MyLogger.getLogger().Log("Finished round "+ (loopCount-1) + " of Repliaca exchange");
			for (Iterator<ReplicaStatisticsHandler> iterator = statsVec.iterator(); iterator.hasNext();) {
				ReplicaStatisticsHandler statH = (ReplicaStatisticsHandler) iterator.next();
				statH.writeTempResultsToLogger(loopCount);
				
			}
		}//end of main loop (main)
		
		//printing duration time for all the algorithm + total number of swaps
		endReplicaTime = System.currentTimeMillis();
		millis = endReplicaTime - startReplicaTime;
		String elapsed = String.format("%d min, %d sec", 
				TimeUnit.MILLISECONDS.toMinutes(millis),
				TimeUnit.MILLISECONDS.toSeconds(millis) - 
				TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
				);
		MyLogger.getLogger().Log("------------------------------------------------");
		MyLogger.getLogger().Log("Finished Replica Exchange Simulation!");
		MyLogger.getLogger().Log("Number Of swaps: " +countSwapsLabels);
		MyLogger.getLogger().Log("Replica Exchange simulation took us: " + elapsed);
		
		
		//writing fitness per temperature statistics to file
		for (Iterator<ReplicaStatisticsHandler> iterator = statsVec.iterator(); iterator.hasNext();) {
			ReplicaStatisticsHandler statH = (ReplicaStatisticsHandler) iterator.next();
			statH.writeResultsToFile();
		}
		return cVec;
	}
}
